/*
 * Copyright (C) 2009 The Guava Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.common.collect;

import static com.google.common.base.Preconditions.checkNotNull;

import com.google.errorprone.annotations.concurrent.LazyInit;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.stream.Collector;

/**
 * GWT emulated version of {@link com.google.common.collect.ImmutableSet}. For the unsorted sets,
 * they are thin wrapper around {@link java.util.Collections#emptySet()}, {@link
 * Collections#singleton(Object)} and {@link java.util.LinkedHashSet} for empty, singleton and
 * regular sets respectively. For the sorted sets, it's a thin wrapper around {@link
 * java.util.TreeSet}.
 *
 * @author Hayward Chan
 * @see ImmutableSortedSet
 */
@SuppressWarnings("serial") // Serialization only done in GWT.
public abstract class ImmutableSet<E> extends ImmutableCollection<E> implements Set<E>
{
    ImmutableSet()
    {
    }

    public static <E> Collector<E, ?, ImmutableSet<E>> toImmutableSet()
    {
        return CollectCollectors.toImmutableSet();
    }

    // Casting to any type is safe because the set will never hold any elements.
    @SuppressWarnings({"unchecked"})
    public static <E> ImmutableSet<E> of()
    {
        return (ImmutableSet<E>) RegularImmutableSet.EMPTY;
    }

    public static <E> ImmutableSet<E> of(E element)
    {
        return new SingletonImmutableSet<E>(element);
    }

    @SuppressWarnings("unchecked")
    public static <E> ImmutableSet<E> of(E e1, E e2)
    {
        return create(e1, e2);
    }

    @SuppressWarnings("unchecked")
    public static <E> ImmutableSet<E> of(E e1, E e2, E e3)
    {
        return create(e1, e2, e3);
    }

    @SuppressWarnings("unchecked")
    public static <E> ImmutableSet<E> of(E e1, E e2, E e3, E e4)
    {
        return create(e1, e2, e3, e4);
    }

    @SuppressWarnings("unchecked")
    public static <E> ImmutableSet<E> of(E e1, E e2, E e3, E e4, E e5)
    {
        return create(e1, e2, e3, e4, e5);
    }

    @SuppressWarnings("unchecked")
    public static <E> ImmutableSet<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E... others)
    {
        int size = others.length + 6;
        List<E> all = new ArrayList<E>(size);
        Collections.addAll(all, e1, e2, e3, e4, e5, e6);
        Collections.addAll(all, others);
        return copyOf(all.iterator());
    }

    public static <E> ImmutableSet<E> copyOf(E[] elements)
    {
        checkNotNull(elements);
        switch (elements.length)
        {
            case 0:
                return of();
            case 1:
                return of(elements[0]);
            default:
                return create(elements);
        }
    }

    public static <E> ImmutableSet<E> copyOf(Collection<? extends E> elements)
    {
        Iterable<? extends E> iterable = elements;
        return copyOf(iterable);
    }

    public static <E> ImmutableSet<E> copyOf(Iterable<? extends E> elements)
    {
        if (elements instanceof ImmutableSet && !(elements instanceof ImmutableSortedSet))
        {
            @SuppressWarnings("unchecked") // all supported methods are covariant
            ImmutableSet<E> set = (ImmutableSet<E>) elements;
            return set;
        }
        return copyOf(elements.iterator());
    }

    public static <E> ImmutableSet<E> copyOf(Iterator<? extends E> elements)
    {
        if (!elements.hasNext())
        {
            return of();
        }
        E first = elements.next();
        if (!elements.hasNext())
        {
            // TODO: Remove "ImmutableSet.<E>" when eclipse bug is fixed.
            return ImmutableSet.<E>of(first);
        }

        Set<E> delegate = Sets.newLinkedHashSet();
        delegate.add(checkNotNull(first));
        do
        {
            delegate.add(checkNotNull(elements.next()));
        }
        while (elements.hasNext());

        return unsafeDelegate(delegate);
    }

    // Factory methods that skips the null checks on elements, only used when
    // the elements are known to be non-null.
    static <E> ImmutableSet<E> unsafeDelegate(Set<E> delegate)
    {
        switch (delegate.size())
        {
            case 0:
                return of();
            case 1:
                return new SingletonImmutableSet<E>(delegate.iterator().next());
            default:
                return new RegularImmutableSet<E>(delegate);
        }
    }

    private static <E> ImmutableSet<E> create(E... elements)
    {
        // Create the set first, to remove duplicates if necessary.
        Set<E> set = Sets.newLinkedHashSet();
        Collections.addAll(set, elements);
        for (E element : set)
        {
            checkNotNull(element);
        }

        switch (set.size())
        {
            case 0:
                return of();
            case 1:
                return new SingletonImmutableSet<E>(set.iterator().next());
            default:
                return new RegularImmutableSet<E>(set);
        }
    }

    @Override
    public boolean equals(Object obj)
    {
        return Sets.equalsImpl(this, obj);
    }

    @Override
    public int hashCode()
    {
        return Sets.hashCodeImpl(this);
    }

    // This declaration is needed to make Set.iterator() and
    // ImmutableCollection.iterator() appear consistent to javac's type inference.
    @Override
    public abstract UnmodifiableIterator<E> iterator();

    abstract static class CachingAsList<E> extends ImmutableSet<E>
    {
        @LazyInit
        private transient ImmutableList<E> asList;

        @Override
        public ImmutableList<E> asList()
        {
            ImmutableList<E> result = asList;
            if (result == null)
            {
                return asList = createAsList();
            }
            else
            {
                return result;
            }
        }

        ImmutableList<E> createAsList()
        {
            return new RegularImmutableAsList<E>(this, toArray());
        }
    }

    abstract static class Indexed<E> extends ImmutableSet<E>
    {
        abstract E get(int index);

        @Override
        public UnmodifiableIterator<E> iterator()
        {
            return asList().iterator();
        }

        @Override
        ImmutableList<E> createAsList()
        {
            return new ImmutableAsList<E>()
            {
                @Override
                public E get(int index)
                {
                    return Indexed.this.get(index);
                }

                @Override
                Indexed<E> delegateCollection()
                {
                    return Indexed.this;
                }
            };
        }
    }

    public static <E> Builder<E> builder()
    {
        return new Builder<E>();
    }

    public static <E> Builder<E> builderWithExpectedSize(int size)
    {
        return new Builder<E>(size);
    }

    public static class Builder<E> extends ImmutableCollection.Builder<E>
    {
        // accessed directly by ImmutableSortedSet
        final ArrayList<E> contents;

        public Builder()
        {
            this.contents = Lists.newArrayList();
        }

        Builder(int initialCapacity)
        {
            this.contents = Lists.newArrayListWithCapacity(initialCapacity);
        }

        @Override
        public Builder<E> add(E element)
        {
            contents.add(checkNotNull(element));
            return this;
        }

        @Override
        public Builder<E> add(E... elements)
        {
            checkNotNull(elements); // for GWT
            contents.ensureCapacity(contents.size() + elements.length);
            super.add(elements);
            return this;
        }

        @Override
        public Builder<E> addAll(Iterable<? extends E> elements)
        {
            if (elements instanceof Collection)
            {
                Collection<?> collection = (Collection<?>) elements;
                contents.ensureCapacity(contents.size() + collection.size());
            }
            super.addAll(elements);
            return this;
        }

        @Override
        public Builder<E> addAll(Iterator<? extends E> elements)
        {
            super.addAll(elements);
            return this;
        }

        Builder<E> combine(Builder<E> builder)
        {
            contents.addAll(builder.contents);
            return this;
        }

        @Override
        public ImmutableSet<E> build()
        {
            return copyOf(contents.iterator());
        }
    }
}
